/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package bakenote.ui;

import bakenote.core.Amount;
import bakenote.core.Formula;
import bakenote.core.Ingredient;
import bakenote.core.Recipe;
import org.apache.pivot.beans.BXML;
import org.apache.pivot.beans.Bindable;
import org.apache.pivot.collections.Dictionary;
import org.apache.pivot.collections.Map;
import org.apache.pivot.collections.List;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.util.Resources;
import org.apache.pivot.wtk.*;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.Insets;
import org.apache.pivot.wtk.Label;
import org.apache.pivot.wtk.content.TableViewNumberCellRenderer;
import org.apache.pivot.wtk.content.TableViewRowEditor;
import org.apache.pivot.wtk.skin.CardPaneSkin.SelectionChangeEffect;
import org.apache.pivot.wtk.skin.terra.TerraTheme;
import org.apache.pivot.wtk.validation.DoubleValidator;

import java.awt.Font;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;

/**
 * @author lello
 */
public class RecipeFormulaPane extends TablePane implements Bindable {

    @BXML
    private TablePane mainFormulaPane = null;
    private List<FormulaTablePane> formulaTablePaneList = new ArrayList<FormulaTablePane>();
    private List<Label> amountList = new ArrayList<Label>();
    private Recipe currentRecipe = null;

    public RecipeFormulaPane() {
    }

    public void initialize(Map<String, Object> map, URL url, Resources rsrcs) {
        mainFormulaPane.getStyles().put("padding", 4);
    }

    public void empty() {
        // Clear the table pane
        TablePane.ColumnSequence cols = mainFormulaPane.getColumns();
        cols.add(new TablePane.Column());
        TablePane.RowSequence rows = mainFormulaPane.getRows();
        // clear the pane from previous recipe
        for (int j = rows.getLength() - 1; j >= 0; j--) {
            rows.remove(j, 1);
        }
    }


    public void setRecipe(Recipe r) {

        // This create a local copy (using the copy constructor) the local copy
        // of the recipe is used for all the calculations to avoid any
        // modification of the recipe in the RecipeList.
        currentRecipe = new Recipe(r);
        //storedRecipe = new Recipe(r);

        // Clear the table pane
        TablePane.ColumnSequence cols = mainFormulaPane.getColumns();
        cols.add(new TablePane.Column());
        TablePane.RowSequence rows = mainFormulaPane.getRows();
        // clear the pane from previous recipe
        for (int j = rows.getLength() - 1; j >= 0; j--) rows.remove(j, 1);

        // clear the lists, so they are dissociated from their parent component.
        //formulaList.clear();
        //formulaPaneList.clear();

        formulaTablePaneList.clear();
        amountList.clear();

        for (Formula f : currentRecipe.getFormulaList()) {

            FormulaTablePane formulaTablePane = new FormulaTablePane(f);
            //formulaTablePaneList.add(formulaTablePane);

            // Create rollup and add the formulaPane
            TerraTheme theme = (TerraTheme) Theme.getTheme();
            //Color color = theme.getColor(2);
            Label formulaNameLabel = new Label();
            formulaNameLabel.getStyles().put("color", theme.getColor(2));
            // Set Formula name
            formulaNameLabel.setText(f.getName());
            if (f.getName().equals("MAIN_FORMULA")) {
                formulaNameLabel.setText("Ingredients");
            }
            formulaNameLabel.getStyles().put("font", new Font("Verdana", Font.BOLD, 16));
            // Create Rollup
            Rollup formulaRollup = new Rollup();
            formulaRollup.setHeading(formulaNameLabel);
            formulaRollup.setExpanded(true);
            formulaRollup.getStyles().put("fill", true);
            // Add a border to the rollup
            Border border = new Border();
            border.setContent(formulaTablePane);
            formulaRollup.setContent(border);

            // Insert the rollup into a TablePane.Row
            TablePane.Row tablePaneRow = new TablePane.Row();
            tablePaneRow.add(formulaRollup);
            rows.add(tablePaneRow);
        }
    }

    private class FormulaTablePane extends TablePane {

        private BoxPane formulaBoxPane = null;
        private TableView formulaTableView = null;
        private BoxPane amountBoxPane = null;
        private Label amountLabelText = new Label("Total:");
        private Label amountDataLabel = null;

        public FormulaTablePane(Formula formula) {

            // create a tableView to show the formula
            createFormulaTableView(formula);

            // create boxPane for the total amount of the formula
            createAmountLabel(formula);

            // Apply styles to the tablePane
            getStyles().put("padding", 4);
            TablePane.ColumnSequence cols = this.getColumns();
            cols.add(new TablePane.Column());
            cols.get(0).setWidth("1*");
            TablePane.Row formulaPaneRow = new TablePane.Row();
            TablePane.Row amountRow = new TablePane.Row();
            formulaPaneRow.add(formulaBoxPane);
            amountRow.add(amountBoxPane);
            TablePane.RowSequence rows = this.getRows();
            rows.add(formulaPaneRow);
            rows.add(amountRow);

        }

        private void createFormulaTableView(final Formula formula) {

            formulaTableView = new TableView();

            TableView.ColumnSequence columns = formulaTableView.getColumns();
            columns.add(new TableView.Column("Name"));
            columns.add(new TableView.Column("Amount"));
            columns.add(new TableView.Column("Percent"));
            columns.get(0).setWidth("5*");
            columns.get(1).setWidth("2*");
            columns.get(2).setWidth("2*");
            // set renderers
            BakerPercentRenderer percentRenderer = new BakerPercentRenderer();
            AmountCellRenderer amountRenderer = new AmountCellRenderer();
            columns.get(2).setCellRenderer(percentRenderer);
            columns.get(1).setCellRenderer(amountRenderer);

            // set TableView properties
            formulaTableView.getStyles().put("font", new Font("Verdana", Font.PLAIN, 12));
            formulaTableView.setTableDataKey("ingredients");
            formulaTableView.load(formula);

            // Install a row editor on each table to edit the amount of each ingredient
            formulaTableView.setRowEditor(new FormulaRowEditor());

            // Create a BoxPane and add the tableView to it
            formulaBoxPane = new BoxPane();
            formulaBoxPane.setOrientation(Orientation.VERTICAL);
            formulaBoxPane.getStyles().put("fill", true);
            formulaBoxPane.getStyles().put("horizontalAlignment", HorizontalAlignment.CENTER);
            formulaBoxPane.add(formulaTableView);

            // The tableView is automatically update when the ingredients weights are recomputed.
            // however we need to update the DataLabel (somehow the binding doesn't work)
            formulaTableView.getTableViewRowListeners().add(new TableViewRowListener.Adapter() {
			@Override
			public void rowUpdated(TableView tv, int index) {
                System.out.println("updated index "+index);
                for (int i = 0; i < amountList.getLength(); i++) {
				    amountList.get(i).load(currentRecipe.getFormula(i));
                }
			}
		    });
        }

        private void createAmountLabel(final Formula formula) {

            amountBoxPane = new BoxPane();
            amountBoxPane.setOrientation(Orientation.HORIZONTAL);
            amountBoxPane.getStyles().put("fill", true);
            amountBoxPane.getStyles().put("horizontalAlignment", HorizontalAlignment.LEFT);
            amountBoxPane.getStyles().put("verticalAlignment", VerticalAlignment.CENTER);
            amountBoxPane.getStyles().put("padding", new Insets(10, 2, 10, 2));
            amountBoxPane.getStyles().put("backgroundColor", 11);
            amountLabelText.getStyles().put("font", new Font("Verdana", Font.PLAIN, 12));
            amountDataLabel = new Label();
            amountDataLabel.getStyles().put("font", new Font("Verdana", Font.BOLD, 12));
            amountDataLabel.setTextKey("amount");
            amountDataLabel.setTextBindMapping(new FormulaAmountLabelMapping());
            amountDataLabel.load(formula);
            amountDataLabel.setPreferredWidth(80);
            amountBoxPane.add(amountLabelText);
            amountBoxPane.add(amountDataLabel);
            // Add the label showing the total amount to the List
            // The list is used to update the formula amounts.
            amountList.add(amountDataLabel);
        }

        private class BakerPercentRenderer extends TableViewNumberCellRenderer {

            private NumberFormat nf = null;

            public BakerPercentRenderer() {
                nf = NumberFormat.getPercentInstance();
                nf.setMaximumFractionDigits(2);
                this.setNumberFormat(nf);
            }

            @Override
            public String toString(Object row, String columnName) {
                Ingredient ing = (Ingredient) row;
                if (!ing.getAmount().isMeasurable()) {
                    return "-";
                } else {
                    return super.toString(row, columnName);
                }
            }
        }

        private class AmountCellRenderer extends TableViewNumberCellRenderer {

            private DecimalFormat nfi = new DecimalFormat("#.#");

            public AmountCellRenderer() {
                //nfi.setMaximumFractionDigits(1);
                //nfi.setMinimumFractionDigits(1);
                //((DecimalFormat) nfi).setDecimalSeparatorAlwaysShown(true);
                this.setNumberFormat(nfi);
            }

            @Override
            public String toString(Object row, String columnName) {
                Ingredient ing = (Ingredient) row;
                Amount amount = ing.getAmount();
                if (amount.isMeasurable()) {
                    return nfi.format(amount.getDouble()) + " " + ing.getUnit();
                } else {
                    if (!ing.getAmount().isNull()) {
                        return ing.getAmount().toString();
                    } else {
                        return "";
                    }
                }
            }
        }

        private class AmountValidator extends DoubleValidator {

            private double min, max;

            /**
             * public AmountValidator(Locale locale, double min, double max) {
             * super(locale);
             * this.min = min;
             * this.max = max;
             * }*
             */
            public AmountValidator(double min, double max) {
                this.min = min;
                this.max = max;
            }

            @Override
            public boolean isValid(String text) {

                boolean valid = false;
                boolean tempValid = false;

                if (text.equals("t.t.") || text.equals("q.b.")) {
                    valid = true;
                } else {
                    // Here we force d to be > than min. The DoubleRangeValidator use the condition d >= min
                    tempValid = super.isValid(text);
                    if (tempValid) {
                        try {
                            //valid = valid && (parseNumber(text).doubleValue() > min);
                            valid = tempValid && (NumberFormat.getInstance().parse(text).doubleValue() > min);
                            //valid = valid && (DecimalFormat.getInstance().parse(text).doubleValue() > min);
                        } catch (ParseException ex) {
                            valid = false;
                            //Logger.getLogger(FormulaPane.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
                return valid;

            }
        }

        private class AmountTextInputMapping implements TextInput.TextBindMapping {

            private DecimalFormat nf = new DecimalFormat("#.#");
            private DecimalFormat amountFormat = new DecimalFormat("#.#");
            private TextInput ti;

            public AmountTextInputMapping(TextInput ti) {
                this.ti = ti;
            }

            @Override
            public String toString(Object value) {
                Amount am = (Amount) value;
                if (am.isMeasurable()) {
                    //System.out.println("amount mapping format measurable " + am.getDouble());
                    ti.setEnabled(true);
                    return amountFormat.format(am.getDouble());
                } else {
                    ti.setEnabled(false);
                    return value.toString();
                }
            }

            @Override
            public Object valueOf(String text) {

                try {
                    //System.out.println("amount mapping valueOf " + text);
                    if (text.equals("t.t.") || text.equals("q.b.")) {
                        //System.out.println("amount mapping valueOf verify " + text);
                        return text;
                    } else {
                        //System.out.println("amount mapping valueOf not verify " + text);
                        return nf.parse(text).doubleValue();
                    }
                } catch (ParseException ex) {
                    //Logger.getLogger(AmountTextInputMapping.class.getName()).log(Level.SEVERE, null, ex);
                    return text;
                }
            }
        }

        private class PercentTextInputMapping implements TextInput.TextBindMapping {

            private NumberFormat percentFormat = NumberFormat.getPercentInstance();
            //private TextInput ti;

            //public PercentTextInputMapping(TextInput ti) {
            public PercentTextInputMapping() {
                percentFormat.setMaximumFractionDigits(2);
                //this.ti = ti;
            }

            @Override
            public String toString(Object value) {
                double percent = Double.valueOf(value.toString());
                if (percent > 0.0d) {
                    //System.out.println("amount mapping format measurable " + am.getDouble());
                    //ti.setEnabled(false);
                    return percentFormat.format(percent);
                } else {
                    //ti.setEnabled(false);
                    return value.toString();
                }
            }

            @Override
            public Object valueOf(String text) {
                try {
                    return percentFormat.parse(text).doubleValue();
                } catch (ParseException ex) {
                    System.out.println("cannot parse percent");
                    //Logger.getLogger(FormulaTablePane.class.getName()).log(Level.SEVERE, null, ex);
                    return text;
                }
            }
        }

        private class FormulaRowEditor extends TableViewRowEditor {

            private TextInput ti = new TextInput();
            private TextInput bti = new TextInput();

            public FormulaRowEditor() {
                ti.setTextKey("Amount");
                ti.setTextBindMapping(new AmountTextInputMapping(ti));
                ti.getStyles().put("font", new Font("Verdana", Font.PLAIN, 12));
                // install the validator
                AmountValidator drvalidator = new AmountValidator(0.0d, 1e10);
                ti.setValidator(drvalidator);
                //ti.getTextInputContentListeners().add(ticl);
                this.getCellEditors().put("Amount", ti);

                // this is a fake editor used to avoid changing the
                // percent format while editing. There might be a better solution.
                bti.setTextKey("Percent");
                bti.setEnabled(false);
                bti.setTextBindMapping(new PercentTextInputMapping());
                bti.getStyles().put("font", new Font("Verdana", Font.PLAIN, 12));
                this.getCellEditors().put("Percent", bti);
                this.setEditEffect(SelectionChangeEffect.CROSSFADE);

            }

            @Override
            public boolean keyPressed(int keyCode, Keyboard.KeyLocation kl) {
                boolean consumed = false;
                // Change row only if the text is valid.
                // The endEdit() method calls store()
                // which gives an error is the text inserted in not valid.
                if (((TextInput) getCellEditors().get("Amount")).isTextValid()) {
                    if (keyCode == Keyboard.KeyCode.ENTER) {
                        endEdit(true);
                        consumed = true;
                    } else if (keyCode == Keyboard.KeyCode.ESCAPE) {
                        endEdit(false);
                        consumed = true;
                    }
                }
                return consumed;
            }

            @Override
            public void endEdit(boolean result) {
                boolean valid = true;
                if (result) {
                    Dictionary<String, Component> editors = getCellEditors();
                    TableView.ColumnSequence cols = getTableView().getColumns();
                    for (int i = 0, n = cols.getLength(); i < n; i++) {
                        Component comp = editors.get(cols.get(i).getName());
                        if (comp instanceof TextInput) {
                            valid &= ((TextInput) comp).isTextValid();
                        }
                    }
                }
                if (!(result && !valid)) {
                    super.endEdit(result);
                }
            }

        }

        private class FormulaAmountLabelMapping implements Label.TextBindMapping {

            private DecimalFormat amountFormat = (DecimalFormat) NumberFormat.getInstance();
            //private NumberFormat amountFormat = NumberFormat.getInstance(Locale.ITALIAN);;

            public String toString(Object o) {
                amountFormat.setMinimumFractionDigits(1);
                amountFormat.setMaximumFractionDigits(1);
                return amountFormat.format(o);
            }

            public Object valueOf(String string) {
                throw new UnsupportedOperationException("Not supported yet.");
            }
        }

    }
}
